/*
 * Copyright (c) 2010-2011, okazoh_tk. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "alkes/text/IGlyphProvider.h"
#include <string.h>

namespace alkes {

template<class T>
int32_t get_char(const void*& p)
{
    int32_t ch = *((const T*)p);

    p = (const uint8_t*)p + sizeof(T);

    // TODO: implement!

    return ch;
}

typedef int32_t (*FuncGetChar)(const void*& p);

FuncGetChar get_get_char_function(IGlyphProvider::Encoding encoding)
{
    static FuncGetChar functions[] = {
        get_char<int8_t>,
        get_char<int16_t>,
        get_char<int32_t>
    };

    return functions[encoding];
}

IGlyphProvider::IGlyphProvider(Encoding encoding)
: encoding_(encoding)
{
}

IGlyphProvider::~IGlyphProvider()
{
}

void IGlyphProvider::setEncoding(Encoding encoding)
{
    encoding_ = encoding;
}

void IGlyphProvider::initTextMetrics(TextMetrics& metrics)
{
    text_metrics_ = metrics;
}

const IGlyphProvider::TextMetrics& IGlyphProvider::getTextMetrics() const
{
    return text_metrics_;
}

bool IGlyphProvider::getGlyphMetrics(int32_t ch, GlyphMetrics& metrics)
{
    return onGetGlyphMetrics(ch, metrics);
}

bool IGlyphProvider::getTextWidth(const void* text, uint16_t& width, uint16_t limit_width)
{
    if (!text)
        return false;

    if (limit_width == 0)
        limit_width = 0x7fff;

    uint16_t pen = 0;
    GlyphMetrics metrics;
    FuncGetChar getChar = get_get_char_function(encoding_);
    int ch;

    while (0 != (ch = getChar(text)) && pen < limit_width)
    {
        onGetGlyphMetrics(ch, metrics);
        pen += metrics.advance;
    }

    width = limit_width < pen ? limit_width : pen;

    return true;
}

bool IGlyphProvider::renderChar(int32_t ch, ImageBuffer& dst, uint16_t x, uint16_t y, GlyphMetrics& metrics)
{
    RenderBuffer target;
    if (!lock(dst, x, y, target))
    {
        // failed to lock buffer
        return false;
    }

    bool ret = onRenderChar(ch, target, metrics);

    unlock(dst);

    return ret;
}

bool IGlyphProvider::renderText(const void* text, ImageBuffer& dst, uint16_t x, uint16_t y, bool clear_first)
{
    if (!text)
    {
        return false;
    }

    RenderBuffer target;
    if (!lock(dst, x, y, target))
    {
        // failed to lock buffer
        return false;
    }

    if (clear_first)
    {
        uint16_t i;
        for (i = 0; i < target.height; ++i)
        {
            memset(target.buffer + i * target.pitch, 0, target.width);
        }
    }

    bool ret = true;
    GlyphMetrics metrics;
    FuncGetChar getChar = get_get_char_function(encoding_);
    int ch;

    while (0 != (ch = getChar(text)))
    {
        ret = onRenderChar(ch, target, metrics);

        if (target.width <= metrics.advance)
            break;

        target.buffer += metrics.advance;
        target.width -= metrics.advance;
    }

    unlock(dst);

    return true;
}

bool IGlyphProvider::lock(ImageBuffer& dst, uint16_t x, uint16_t y, RenderBuffer& target)
{
    if (AL_PIXEL_FORMAT_A8 != dst.getPixelFormat())
    {
        return false;
    }

    uint16_t w, h;
    dst.getImageSize(w, h);
    if (w <= x || h <= y)
    {
        return false;
    }

    int32_t pitch;
    Rect rect = {
        x, y, w - x, h - y,
    };
    uint8_t* tmp = (uint8_t*)dst.lockRect(&rect, AL_LOCKMODE_WRITE, &pitch);
    if (!tmp)
    {
        // failed to lock buffer
        return false;
    }

    target.buffer = tmp;
    target.pitch = pitch;
    target.width = w - x;
    target.height = h - y;

    return true;
}

void IGlyphProvider::unlock(ImageBuffer& dst)
{
    dst.unlockRect();

}

}
